﻿using System;
using System.Linq;
using Microsoft.Practices.Unity.InterceptionExtension;
using VerifyArgs;
using EP.Caching.Providers;
using System.Runtime.Caching;
using System.Collections.Generic;
using System.Data.SqlClient;
using EP.Caching.Attributes;
using EP.Caching.KeyGen;

namespace EP.Caching {
	public class CacheCallHandlerInvoke : ICallHandler {
		public CacheScope Scope {
			get { return _cacheKeyGenerator.Scope; }
			set { _cacheKeyGenerator.Scope = value; }
		}

		private readonly ICacheProvider _cache;
		private readonly ICacheKeyGenerator _cacheKeyGenerator;

		public CacheCallHandlerInvoke(ICacheProvider cache, ICacheKeyGenerator cacheKeyGenerator) {
			Verify.Args(new { cache, cacheKeyGenerator }).NotNull();

			_cache = cache;
			_cacheKeyGenerator = cacheKeyGenerator;
		}

		#region ICallHandler Members
		public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext) {
			Verify.Args(new { input }).NotNull();

			string key = ComputeCacheKey(input);

			//check invalidate triggers
			InvalidateCache(input, key);

			//check in cache
			object cachedObject = _cache.Get(key);
			if (cachedObject != null)
				//found in cache
				return input.CreateMethodReturn(cachedObject, null);

			//execute method
			IMethodReturn RetVal = getNext()(input, getNext);

			AddToCache(input, key, RetVal.ReturnValue);

			return RetVal;
		}
		public int Order { get; set; }
		#endregion

		private string ComputeCacheKey(IMethodInvocation input) {
			return _cacheKeyGenerator.GetKey(input.MethodBase, input.Arguments, input.Target);
		}
		private void InvalidateCache(IMethodInvocation input, string key) {
			Verify.NotNull(new { input, input.MethodBase });

			var invalidateTriggers = input.MethodBase.GetCustomAttributes(typeof(InvalidateCacheTriggerAttribute), true) as InvalidateCacheTriggerAttribute[];
			if (invalidateTriggers != null) {
				foreach (var invalidateTrigger in invalidateTriggers.OrderBy(s => s.Order)) {
					if (!invalidateTrigger.IsValid(input.MethodBase, input.Target)) {
						_cache.Remove(key);
						return;
					}
				}
			}
		}
		private void AddCacheTimeouts(IMethodInvocation input) {
			var timeouts = input.MethodBase.GetCustomAttributes(typeof(CacheTimeoutAttribute), true) as CacheTimeoutAttribute[];
			if (timeouts != null && timeouts.Any()) {
				var timeout = timeouts.First();

				if (timeout.SlidingTimeout != null && timeout.SlidingTimeout.HasValue) {
					_cache.SlidingTimeout = timeout.SlidingTimeout.Value;
				}

				if (_cache.SupportsAbsoluteTimeout && timeout.AbsoluteTimeout != null && timeout.AbsoluteTimeout.HasValue) {
					_cache.AbsoluteTimeout = timeout.AbsoluteTimeout.Value;
				}
			}
		}
		private void AddCacheDependencies(IMethodInvocation input) {
			AddFileDependencies(input);

			var sqlExpiry = input.MethodBase.GetCustomAttributes(typeof(SqlCacheExpiryAttribute), true) as SqlCacheExpiryAttribute[];
			if (sqlExpiry != null && sqlExpiry.Any()) {
				foreach (var item in sqlExpiry) {
					var sqlDependency = new SqlDependency();
					_cache.Monitors.Add(new SqlChangeMonitor(sqlDependency));
				}
			}
		}
		private void AddFileDependencies(IMethodInvocation input) {
			var fileExpiry = input.MethodBase.GetCustomAttributes(typeof(FileCacheExpiryAttribute), true) as FileCacheExpiryAttribute[];
			if (fileExpiry != null && fileExpiry.Any()) {
				IList<string> filePaths = new List<string>();
				foreach (var item in fileExpiry) {
					filePaths.Add(item.FileName);
				}

				_cache.Monitors.Add(new HostFileChangeMonitor(filePaths));
			}
		}
		private void AddToCache(IMethodInvocation input, string key, object value) {
			AddCacheTimeouts(input);

			if (_cache.SupportsChangeMonitors) {
				AddCacheDependencies(input);
			}

			_cache.Add(key, value);
		}
	}
}